package repository

import (
	"fmt"
	"net/http"
	"net/url"
	"server/components/errors"
	"server/components/models/ent"
	"server/components/models/ent/schema"
	"strconv"
	"strings"
	"time"

	"github.com/araddon/dateparse"
	"github.com/relvacode/iso8601"
)

//////////////////////////////////////////////////////////////////////////////////////////////////
/// Models for REST API
//////////////////////////////////////////////////////////////////////////////////////////////////

type OsmData struct {
	MapLink          string                       `json:"mapLink"`
	BoundingBox      []string                     `json:"boundingBox,omitempty"`
	PointsOfInterest []schema.OsmPointsOfInterest `json:"pointsOfInterest,omitempty"`
	IncludePOI       interface{}                  `json:"includePOI,omitempty"`
}

func (o *OsmData) IsIncludePOI() bool {
	if o.IncludePOI == nil {
		return false
	}
	switch v := o.IncludePOI.(type) {
	case bool:
		return v
	case string:
		return strings.EqualFold(v, "true")
	}
	return false
}

func convertSFloatVec(v []string) ([]float64, errors.RequestError) {
	vec := make([]float64, len(v))
	for index := range v {
		if val, err := strconv.ParseFloat(v[index], 64); err != nil {
			return nil, errors.New("некорректный формат числа", http.StatusBadRequest)
		} else {
			vec[index] = val
		}
	}
	return vec, nil
}

func convertFVecToSVec(v []float64) []string {
	vec := make([]string, len(v))
	for index := range v {
		vec[index] = fmt.Sprintf("%v", v[index])
	}
	return vec
}

type Region struct {
	ID           int64    `json:"id,omitempty"`
	Name         string   `json:"name,omitempty"`
	ParentRegion string   `json:"parentRegion,omitempty"`
	Latitude1    float64  `json:"latitude1,omitempty,string"`
	Longitude1   float64  `json:"longitude1,omitempty,string"`
	Latitude2    float64  `json:"latitude2,omitempty,string"`
	Longitude2   float64  `json:"longitude2,omitempty,string"`
	RegionType   int64    `json:"regionType,omitempty"`
	AccountId    int      `json:"accountId,omitempty"`
	OsmData      *OsmData `json:"osmData,omitempty"`
}

type AddRegionRequest struct {
	Name         *string  `json:"name,omitempty"`
	ParentRegion *string  `json:"parentRegion,omitempty"`
	Latitude1    float64  `json:"latitude1,omitempty,string"`
	Longitude1   float64  `json:"longitude1,omitempty,string"`
	Latitude2    float64  `json:"latitude2,omitempty,string"`
	Longitude2   float64  `json:"longitude2,omitempty,string"`
	RegionType   *int64   `json:"regionType,omitempty"`
	OsmData      *OsmData `json:"osmData,omitempty"`
}

type RegionType struct {
	ID   int64  `json:"id,omitempty"`
	Type string `json:"type,omitempty"`
}

type AddRegionTypeRequest struct {
	Type *string `json:"type,omitempty"`
}

type UpdateRegionRequest struct {
	Name         *string  `json:"name,omitempty"`
	ParentRegion *string  `json:"parentRegion,omitempty"`
	Latitude1    *float64 `json:"latitude1,omitempty,string"`
	Longitude1   *float64 `json:"longitude1,omitempty,string"`
	Latitude2    *float64 `json:"latitude2,omitempty,string"`
	Longitude2   *float64 `json:"longitude2,omitempty,string"`
	RegionType   *int64   `json:"regionType,omitempty"`
	OsmData      *OsmData `json:"osmData,omitempty"`
}

type UpdateRegionTypeRequest struct {
	Type *string `json:"type,omitempty"`
}

func (r *Region) FromEnt(region *ent.Region) *Region {
	if region.Edges.AccountId != nil {
		r.AccountId = region.Edges.AccountId.ID
	}
	r.Latitude1 = region.Latitude1
	r.Longitude1 = region.Longitude1
	r.Latitude2 = region.Latitude2
	r.Longitude2 = region.Longitude2
	r.Name = region.Name
	r.ParentRegion = region.ParentRegion
	if region.Edges.RegionType != nil {
		r.RegionType = region.Edges.RegionType.ID
	}
	return r
}

type Weather struct {
	ID                  int64      `json:"id,omitempty"`
	Temperature         float64    `json:"temperature,omitempty,string"`
	Humidity            float64    `json:"humidity,omitempty,string"`
	WindSpeed           float64    `json:"windSpeed,omitempty,string"`
	WeatherCondition    string     `json:"weatherCondition,omitempty"`
	PrecipitationAmount float64    `json:"precipitationAmount,omitempty,string"`
	MeasurementDateTime string     `json:"measurementDateTime,omitempty"`
	RegionName          string     `json:"regionName,omitempty"`
	WeatherForecast     []Forecast `json:"weatherForecast,omitempty"`
}

type WeatherSearchResult struct {
	ID                  int64   `json:"id,omitempty"`
	Temperature         float64 `json:"temperature,omitempty,string"`
	Humidity            float64 `json:"humidity,omitempty,string"`
	WindSpeed           float64 `json:"windSpeed,omitempty,string"`
	WeatherCondition    string  `json:"weatherCondition,omitempty"`
	PrecipitationAmount float64 `json:"precipitationAmount,omitempty,string"`
	MeasurementDateTime string  `json:"measurementDateTime,omitempty"`
	RegionName          string  `json:"regionName,omitempty"`
	WeatherForecast     []int64 `json:"weatherForecast,omitempty"`
}

type AddWeatherRequest struct {
	RegionId            int64   `json:"regionId"`
	Temperature         float64 `json:"temperature,omitempty,string"`
	Humidity            float64 `json:"humidity,omitempty,string"`
	WindSpeed           float64 `json:"windSpeed,omitempty,string"`
	WeatherCondition    string  `json:"weatherCondition,omitempty"`
	PrecipitationAmount float64 `json:"precipitationAmount,omitempty,string"`
	MeasurementDateTime string  `json:"measurementDateTime,omitempty"`
	WeatherForecast     []int64 `json:"weatherForecast,omitempty"`
}

type UpdateWeatherRequest struct {
	RegionName          *string  `json:"regionName,omitempty"`
	Temperature         *float64 `json:"temperature,omitempty,string"`
	Humidity            *float64 `json:"humidity,omitempty,string"`
	WindSpeed           *float64 `json:"windSpeed,omitempty,string"`
	WeatherCondition    *string  `json:"weatherCondition,omitempty"`
	PrecipitationAmount *float64 `json:"precipitationAmount,omitempty,string"`
	MeasurementDateTime *string  `json:"measurementDateTime,omitempty"`
	WeatherForecast     []int64  `json:"weatherForecast,omitempty"`
}

type SearchWeatherRequest struct {
	SearchRequest
	RegionId         *int64  `json:"regionId,omitempty"`
	StartDateTime    *string `json:"startDateTime,omitempty"`
	EndDateTime      *string `json:"endDateTime,omitempty"`
	WeatherCondition *string `json:"weatherCondition,omitempty"`
}

func (s *SearchWeatherRequest) FromQuery(u *url.URL) errors.RequestError {
	if err := s.SearchRequest.FromQuery(u); err != nil {
		return errors.New("Некорректные параметры from/size", http.StatusBadRequest)
	}
	rId := u.Query().Get("regionId")
	if rId != "" {
		if val, err := strconv.ParseInt(rId, 10, 64); err != nil {
			return errors.New("Некорректный параметр regionId", http.StatusBadRequest)
		} else {
			s.RegionId = &val
		}
	}
	if st := u.Query().Get("startDateTime"); st != "" {
		s.StartDateTime = &st
	}
	if et := u.Query().Get("endDateTime"); et != "" {
		s.EndDateTime = &et
	}
	if cond := u.Query().Get("weatherCondition"); cond != "" {
		s.WeatherCondition = &cond
	}
	return nil
}

type Forecast struct {
	ID                  int64   `json:"id"`
	RegionId            int64   `json:"regionId"`
	Temperature         float64 `json:"temperature,omitempty,string"`
	WeatherCondition    string  `json:"weatherCondition,omitempty"`
	PrecipitationAmount float64 `json:"precipitationAmount,omitempty,string"`
	WindSpeed           float64 `json:"windSpeed,omitempty,string"`
	DateTime            string  `json:"dateTime,omitempty"`
}

type ForecastUpdateRequest struct {
	Temperature         *float64 `json:"temperature,omitempty,string"`
	WeatherCondition    *string  `json:"weatherCondition,omitempty"`
	DateTime            *string  `json:"dateTime,omitempty"`
	PrecipitationAmount *float64 `json:"precipitationAmount,omitempty,string"`
	WindSpeed           *float64 `json:"windSpeed,omitempty,string"`
}

type ForecastAddRequest struct {
	RegionId            int64    `json:"regionId"`
	Temperature         float64  `json:"temperature,omitempty,string"`
	WeatherCondition    string   `json:"weatherCondition,omitempty"`
	DateTime            string   `json:"dateTime,omitempty"`
	PrecipitationAmount *float64 `json:"precipitationAmount,omitempty,string"`
	WindSpeed           *float64 `json:"windSpeed,omitempty,string"`
}

func ParseTime(v string) (time.Time, error) {
	if parsed, err := iso8601.ParseString(v); err == nil {
		return parsed, nil
	}
	if parsed, err := time.Parse(time.RFC3339, v); err == nil {
		return parsed, nil
	}
	if parsed, err := time.Parse("2006-01-02T15:04:05", v); err == nil {
		return parsed, nil
	}
	if parsed, err := time.Parse("2006-01-02", v); err == nil {
		return parsed, nil
	}
	if parsed, err := dateparse.ParseAny(v); err == nil {
		return parsed, nil
	}
	return time.Time{}, fmt.Errorf("cannot parse time %v", v)
}
